/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package ga_checkpoint_1;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;

/**
 *
 * @author Asif Dipon, Harshal Joshi
 */
public class Main {

    //constant static variables that define the max number of generations the
    //algorithm should run for and the range in which the entity's chromosomes
    //may fall under
    final static int MAX_NUM_GENERATION = 10;
    final static double RANGE_MIN = -5;
    final static double RANGE_MAX = 5;

    /**The main method. It processes command line arguments and starts the
     * primary program.
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        doTask();
    }

    /**
     * This runs the algorithm. It creates the 0th generation. Then it spawns 10
     * new generations through crossover and mutation. At each generation, the 10
     * fittest entities survive to live into the next generation.
     */
    public static void doTask(){
        //Create the 0th generation with random values.
        List<Entity> population = createGenZero();
        int genNum = 0;
        //Print the info for the 0th generation.
        printInfo(population, genNum);
        //Create new generations until the limit is reached.
        while(genNum <= MAX_NUM_GENERATION){
            //Create a list of child entities that will be created through crossover and mutation
            List<Entity> spawn = new ArrayList<Entity>();
            //Do crossover and mutation 5 times each
            for( int i = 0; i<5; i++){
                //create spawn through crossover
                Entity p1 = selectRandomFromPopulation(population);
                Entity p2 = selectRandomFromPopulation(population);
                Entity child1 = doCrossover(p1, p2);
                spawn.add(child1);
                //create spawn through mutation
                Entity p3 = selectRandomFromPopulation(population);
                Entity child2 = doMutation(p3);
                spawn.add(child2);
            }
            //add all spawn to the population
            population.addAll(spawn);
            //Sort the population based on fitness, in ascending order.
            Collections.sort(population);
            //select top 10 fittest in the population
            int size = population.size();
            List<Entity> top10 = new ArrayList(population.subList(size-10, size));
            //Remove all entities in the population that are not in the top10.
            population.clear();
            for(Entity e: top10){
                population.add(e);
            }
            //Print the info for the generation
            genNum++;
            printInfo(population, genNum);
            
        }        
    }

    /**
     * This method creates generation 0 through random assignment of chromosomes.
     * @return A list containing 10 randomly generated entities.
     */
    public static List<Entity> createGenZero(){
        List<Entity> population = new ArrayList<Entity>();
        for(int i =0; i<10; i++){
            double x1 = getRandomDouble(RANGE_MIN, RANGE_MAX);
            double x2 = getRandomDouble(RANGE_MIN, RANGE_MAX);
            int uid = UIDGen.getInstance().getNextUID();
            Entity e = new Entity(uid, x1, x2);
            population.add(e);
        }
        return population;
    }

    /**
     * This method takes two parent entities and creates a child entity
     * through crossover of genes.
     * @param parent1   The first parent to take genes from.
     * @param parent2   The second parent to take genes from
     * @return  A new child entity
     */
    public static Entity doCrossover(Entity parent1, Entity parent2){
    	Entity child = new Entity(UIDGen.getInstance().getNextUID(), 0, 0);
    	child.genes[0] = parent1.genes[0];
    	child.genes[1] = parent2.genes[1];
        child.updateFitness();
    	return child;
    }

    /**
     * This method creates a child entity through the mutation of one or more
     * genes of a parent entity.
     * @param parent    The entity whose genes will mutate
     * @return  A child entity that is different from its sole parent
     */
    public static Entity doMutation(Entity parent){
    	//choose random number [0,2]
    	Random rand = new Random();
       	int randNum = rand.nextInt(3);
       	Entity child = new Entity(UIDGen.getInstance().getNextUID(), 0, 0);       	
       	switch (randNum){
       		case 0: child.genes[0] = getRandomDouble(RANGE_MIN, RANGE_MAX); break;
       		case 1: child.genes[1] = getRandomDouble(RANGE_MIN, RANGE_MAX); break;
       		case 2: child.genes[0] = getRandomDouble(RANGE_MIN, RANGE_MAX); child.genes[1] = getRandomDouble(RANGE_MIN, RANGE_MAX);break;
       	}
        child.updateFitness();
       	return child;
    }
    
    /**
     * This method prints the relevant information for each generation: generation
     * number, the min/max fitness, and the population's information.
     * @param population    The list containing the population.
     * @param genNum    The generation number
     */
    public static void printInfo(List<Entity> population, int genNum){
        //Print the generation number
        System.out.println("\nGeneration #" + genNum);
        //Sort the population
        Collections.sort(population);
        //Print out the min and max fitness among the population
        System.out.println("\nMin fitness: " + population.get(0).fitness);
        System.out.println("Max fitness: " + population.get(population.size()-1).fitness);
        //Print out each individual in the population and their information.
        System.out.println("\nID \t\t Fitness \t\t\t Genes");
        for(Entity e : population){
            System.out.println(e.id + " \t " + e.fitness + " \t (" + e.genes[0]
                    + ", " + e.genes[1] + ")");
        }
        
    }

    /**
     * Returns a pseudorandom member from a population
     * @param pop
     * @return
     */
    public static Entity selectRandomFromPopulation(List<Entity> pop){
        Random rand = new Random();
        int index = rand.nextInt(pop.size());
        return pop.get(index);
    }

    /**
     * Returns a random double between the ranges specified.
     * @param rangeMin  The lower bound
     * @param rangeMax  The upper bound
     * @return  A pseudorandom double within the specified range.
     */
    public static double getRandomDouble(double rangeMin, double rangeMax){
        double retVal = 0;
        Random rand = new Random();
        retVal = ((rangeMax-rangeMin) * rand.nextDouble()) + rangeMin;
        return retVal;
    }

}
